package cloud.pangui.core.lock.aop;

import cloud.pangui.core.lock.annotation.GlobalLock;
import cloud.pangui.core.lock.exception.GlobalLockException;
import cloud.pangui.core.lock.executor.LockExecutor;
import cloud.pangui.core.lock.resolver.LockResolver;
import cloud.pangui.core.lock.resolver.LockResolverFactory;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @Auther hepangui
 * @Date 2020/10/9 0009
 */
@Component
@Aspect
@Order(Integer.MIN_VALUE)
public class LockAspect {

	Logger logger = LoggerFactory.getLogger(LockAspect.class);

	@Autowired
	LockResolverFactory lockResolverFactory;

	@Autowired
	LockExecutor lockExecutor;

	/**
	 * 锁标记，当前线程内同一个锁不再重新加锁
	 */
	ThreadLocal<Map<String, String>> currentLock = new ThreadLocal<>();

	/**
	 * 切面点，支持globalLock注解
	 *
	 * @param point      切面
	 * @param globalLock 将注解带入进来
	 * @return
	 * @throws Throwable
	 */
	@Around("@annotation(globalLock)")
	public Object around(ProceedingJoinPoint point, GlobalLock globalLock) throws Throwable {
		Signature sig = point.getSignature();
		MethodSignature msig = null;
		if (!(sig instanceof MethodSignature)) {
			throw new IllegalArgumentException("该注解只能用于方法");
		}
		msig = (MethodSignature) sig;
		Object target = point.getTarget();
		Method method = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
		Object[] args = point.getArgs();
		LockResolver resolver = lockResolverFactory.getResolver(method);

		if (logger.isTraceEnabled()) {
			logger.trace("[全局锁] " + target.getClass() + "#" + method.getName() + "onbefore");
		}
		long timeout = globalLock.timeout();
		long expireTime = globalLock.expireTime();
		boolean useLock = resolver.checkConidtion(target, args);
		if (!useLock) {
			if (logger.isTraceEnabled()) {
				logger.trace("[全局锁] " + target.getClass() + "#" + method.getName() + "当前条件不加锁");
			}
			return point.proceed();
		}
		String key = resolver.resolverKey(target, args);
		if (logger.isTraceEnabled()) {
			logger.trace("[全局锁] key:" + key);
		}
		//可重入，并且此时已经获取到了一个相同的锁，则不再需要获取锁
		if (currentLock.get() != null && currentLock.get().get(key) != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("[全局锁] 当前已经获取到锁:" + target.getClass() + "#" + method.getName() + key);
			}
			return point.proceed();
		}
		String selfmark = UUID.randomUUID().toString();
		boolean lockSuccess = tryLock(key, selfmark, expireTime, timeout, globalLock.interval());
		if (logger.isTraceEnabled()) {
			logger.trace("[全局锁] 获取锁" + (lockSuccess ? "成功:" : "失败:") + key);
		}
		if (!lockSuccess) {
			if (logger.isTraceEnabled()) {
				logger.trace("[全局锁] " + target.getClass() + "#" + method.getName() + "没有获取到锁" + key);
			}
			//有callback ，执行callback
			if (globalLock.callback() != null && !"".equals(globalLock.callback())) {
				Class<?> returnType = resolver.getMethod().getReturnType();
				if (returnType == Void.TYPE) {
					resolver.callbackNoReturn(target, args);
					return null;
				} else {
					return resolver.callback(target, args);
				}
			} else {
				throw new GlobalLockException("[全局锁] 没有获取到锁");
			}
		} else {//可重入标记
			if (currentLock.get() == null) {
				currentLock.set(new HashMap<>());
			}
			currentLock.get().put(key, selfmark);
			try {
				return point.proceed();
			} finally {
				if (logger.isTraceEnabled()) {
					logger.trace("[全局锁] 释放锁" + target.getClass() + "#" + method.getName() + key);
				}
				unlock(key, selfmark);
				currentLock.get().remove(key);
			}
		}
	}

	public boolean tryLock(String key, String selfMark, long expireTime, long timeout, long interval) {
		if (logger.isTraceEnabled()) {
			logger.trace("[全局锁] trylock:" + key);
		}

		if (interval <= 0) {
			interval = 300L;
		}

		if (timeout > 0) {
			long begin = System.currentTimeMillis();
			while (System.currentTimeMillis() - begin < timeout) {
				lockExecutor.lock(key, selfMark, expireTime);
				if (logger.isTraceEnabled()) {
					logger.trace("[全局锁] " + key + "没有获取到锁，等待中");
				}
				synchronized (Thread.currentThread()) {
					try {
						Thread.currentThread().wait(interval);
					} catch (InterruptedException e) {
						return false;
					}
				}
			}
			return false;
		} else {
			return lockExecutor.lock(key, selfMark, expireTime);
		}

	}


	public void unlock(String key, String selfMark) {
		if (logger.isTraceEnabled()) {
			logger.trace("[全局锁] unlock:" + key);
		}
		lockExecutor.release(key, selfMark);
	}

}
